﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Collections;

namespace MvcSupplantCaching.UnitTests
{
    [TestClass]
    public class BaseOutputCacheProviderTest
    {
        private BaseOutputCacheProvider _provider;
        private OutputCacheManager _manager;
        private static IDictionary DependencyCache = new Dictionary<string, object>();
        private const int ShortExpirationSeconds = 1;
        private const int LongExpirationSeconds = 300;

        [TestInitialize]
        public void Initialize()
        {
            _provider = OutputCache.Instance;
            _manager = new OutputCacheManager(DependencyCache);
            _manager.RequestCache[OutputCache.UrlCacheKey] = "/";
        }

        [TestMethod]
        public void AddTest()
        {
            const string testKey = "add-test-key";
            const string testVal = "testValue";
            _provider.Add(testKey, testVal, DateTime.UtcNow.AddSeconds(LongExpirationSeconds));
            string result = (string)_provider.Get(testKey);
            Assert.AreEqual(testVal, result);
        }

        [TestMethod]
        public void AddWithExpirationTest()
        {
            const string testKey = "add-test-expiration-key";
            const string testVal = "testValue";
            _provider.Add(testKey, testVal, DateTime.UtcNow.AddSeconds(ShortExpirationSeconds));
            string result = (string)_provider.Get(testKey);
            Assert.AreEqual(testVal, result);

            Thread.Sleep(4 * 1000);
            result = (string)_provider.Get(testKey);
            Assert.IsNull(result);
        }

        [TestMethod]
        public void RemoveTest()
        {
            const string testKey = "remove-key";
            const string testVal = "testValue";
            _provider.Add(testKey, testVal, DateTime.UtcNow.AddSeconds(LongExpirationSeconds));
            string result = (string)_provider.Get(testKey);
            Assert.AreEqual(testVal, result);

            _provider.Remove(testKey);
            result = (string)_provider.Get(testKey);
            Assert.IsNull(result);
        }

        [TestMethod]
        public void RemoveCallbackTest()
        {
            const string testKey = "remove-callback-key";
            const string testVal = "testValue";

            // Add it to the cache
            _provider.Add(testKey, testVal, DateTime.UtcNow.AddSeconds(ShortExpirationSeconds));
            string result = (string)_provider.Get(testKey);
            Assert.AreEqual(testVal, result);

            // Add an item with the same key to the supplant cache.
            SupplantCache.Cache.Add(testKey, testVal, DateTime.UtcNow.AddSeconds(LongExpirationSeconds));
            result = (string)SupplantCache.Cache[testKey];
            Assert.AreEqual(testVal, result);

            Thread.Sleep(4 * 1000);
            result = (string)_provider.Get(testKey);
            Assert.IsNull(result);

            result = (string)SupplantCache.Cache.Get(testKey);
            Assert.IsNull(result);
        }

        [TestMethod]
        public void SetNewTest()
        {
            const string testKey = "set-key";
            const string testVal = "testValue";

            // Set it in the cache
            _provider.Set(testKey, testVal, DateTime.UtcNow.AddSeconds(LongExpirationSeconds));
            string result = (string)_provider.Get(testKey);
            Assert.AreEqual(testVal, result);
        }

        [TestMethod]
        public void SetExistingTest()
        {
            const string testKey = "set-key";
            const string testVal = "testValue";
            const string newVal = "newValue";

            // Add it to the cache
            _provider.Add(testKey, testVal, DateTime.UtcNow.AddSeconds(LongExpirationSeconds));
            string result = (string)_provider.Get(testKey);
            Assert.AreEqual(testVal, result);

            // Alter it in the cache
            _provider.Set(testKey, newVal, DateTime.UtcNow.AddSeconds(LongExpirationSeconds));
            result = (string)_provider.Get(testKey);
            Assert.AreEqual(newVal, result);
        }

        [TestMethod]
        public void AddOutputCacheDependencyTest()
        {
            const string dependencyKey = "testKey";
            const string outputCacheKey = "testOutputCacheKey";
            object outputCacheItem = new object();
            _provider.Add(outputCacheKey, outputCacheItem, DateTime.UtcNow.AddMinutes(5));

            object result = _provider.Get(outputCacheKey);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            _manager.AddOutputCacheDependency(dependencyKey);
            DependencyCache[OutputCache.CacheKey] = outputCacheKey;
            _manager.FinalizeDependencies();
            result = _provider.Get(dependencyKey);
            Assert.IsInstanceOfType(result, typeof(CacheDependencyItem));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey));
        }

        [TestMethod]
        public void AddOutputCacheDependenciesTest()
        {
            const string dependencyKey = "testKey";
            const string outputCacheKey = "testOutputCacheKey";
            const string outputCacheKey2 = "testOutputCacheKey2";
            const string outputCacheKey3 = "testOutputCacheKey3";
            object outputCacheItem = new object();
            _provider.Add(outputCacheKey, outputCacheItem, DateTime.UtcNow.AddMinutes(5));

            _provider.Add(outputCacheKey2, outputCacheItem, DateTime.UtcNow.AddMinutes(5));

            _provider.Add(outputCacheKey3, outputCacheItem, DateTime.UtcNow.AddMinutes(5));
            object result = _provider.Get(outputCacheKey);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            result = _provider.Get(outputCacheKey2);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            result = _provider.Get(outputCacheKey3);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            _manager.AddOutputCacheDependency(dependencyKey);
            _manager.AddOutputCacheDependency(dependencyKey);
            _manager.AddOutputCacheDependency(dependencyKey);

            DependencyCache[OutputCache.CacheKey] = outputCacheKey;
            _manager.FinalizeDependencies();
            DependencyCache[OutputCache.CacheKey] = outputCacheKey2;
            _manager.FinalizeDependencies();
            DependencyCache[OutputCache.CacheKey] = outputCacheKey3;
            _manager.FinalizeDependencies();
            result = _provider.Get(dependencyKey);
            Assert.IsInstanceOfType(result, typeof(CacheDependencyItem));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey2));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey3));
        }

        [TestMethod]
        public void AddOutputCacheUrlDependencyTest()
        {
            const string url = "/forums/test/123-asdf";
            const string outputCacheKey = "testOutputCacheKey";
            const string outputCacheKey2 = "testOutputCacheKey2";
            const string outputCacheKey3 = "testOutputCacheKey3";
            object outputCacheItem = new object();
            _provider.Add(outputCacheKey, outputCacheItem, DateTime.UtcNow.AddMinutes(5));

            _provider.Add(outputCacheKey2, outputCacheItem, DateTime.UtcNow.AddMinutes(5));

            _provider.Add(outputCacheKey3, outputCacheItem, DateTime.UtcNow.AddMinutes(5));
            object result = _provider.Get(outputCacheKey);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            result = _provider.Get(outputCacheKey2);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            result = _provider.Get(outputCacheKey3);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            _manager.AddOutputCacheDependency(url);

            DependencyCache[OutputCache.CacheKey] = outputCacheKey;
            _manager.FinalizeDependencies();
            DependencyCache[OutputCache.CacheKey] = outputCacheKey2;
            _manager.FinalizeDependencies();
            DependencyCache[OutputCache.CacheKey] = outputCacheKey3;
            _manager.FinalizeDependencies();
            result = _provider.Get(url);
            Assert.IsInstanceOfType(result, typeof(CacheDependencyItem));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey2));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey3));
        }

        [TestMethod]
        public void ExpireOutputCacheDependencyTest()
        {
            const string dependencyKey = "testKey";
            const string outputCacheKey = "testOutputCacheKey";
            object outputCacheItem = new object();
            _provider.Add(outputCacheKey, outputCacheItem, DateTime.UtcNow.AddMinutes(5));
            object result = _provider.Get(outputCacheKey);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            _manager.AddOutputCacheDependency(dependencyKey);
            DependencyCache[OutputCache.CacheKey] = outputCacheKey;
            _manager.FinalizeDependencies();
            result = _provider.Get(dependencyKey);
            Assert.IsInstanceOfType(result, typeof(CacheDependencyItem));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey));

            // Remove it
            _provider.Remove(dependencyKey);
            result = _provider.Get(outputCacheKey);
            Assert.IsNull(result);
        }

        [TestMethod]
        public void ExpireOutputCacheDependenciesTest()
        {
            const string dependencyKey = "testKey";
            const string outputCacheKey = "testOutputCacheKey";
            const string outputCacheKey2 = "testOutputCacheKey2";
            const string outputCacheKey3 = "testOutputCacheKey3";
            object outputCacheItem = new object();
            _provider.Add(outputCacheKey, outputCacheItem, DateTime.UtcNow.AddMinutes(5));
            _provider.Add(outputCacheKey2, outputCacheItem, DateTime.UtcNow.AddMinutes(5));
            _provider.Add(outputCacheKey3, outputCacheItem, DateTime.UtcNow.AddMinutes(5));
            object result = _provider.Get(outputCacheKey);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            result = _provider.Get(outputCacheKey2);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            result = _provider.Get(outputCacheKey3);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            _manager.AddOutputCacheDependency(dependencyKey);

            DependencyCache[OutputCache.CacheKey] = outputCacheKey;
            _manager.FinalizeDependencies();
            DependencyCache[OutputCache.CacheKey] = outputCacheKey2;
            _manager.FinalizeDependencies();
            DependencyCache[OutputCache.CacheKey] = outputCacheKey3;
            _manager.FinalizeDependencies();
            result = _provider.Get(dependencyKey);
            Assert.IsInstanceOfType(result, typeof(CacheDependencyItem));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey2));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey3));

            // Remove it
            _provider.Remove(dependencyKey);
            result = _provider.Get(outputCacheKey);
            Assert.IsNull(result);
            result = _provider.Get(outputCacheKey2);
            Assert.IsNull(result);
            result = _provider.Get(outputCacheKey3);
            Assert.IsNull(result);
        }

        [TestMethod]
        public void ExpireOutputCacheByUrlTest()
        {
            const string url = "/forums/test/123-asdf";
            const string outputCacheKey = "testOutputCacheKey";
            const string outputCacheKey2 = "testOutputCacheKey2";
            const string outputCacheKey3 = "testOutputCacheKey3";
            object outputCacheItem = new object();
            _provider.Add(outputCacheKey, outputCacheItem, DateTime.UtcNow.AddMinutes(5));
            _provider.Add(outputCacheKey2, outputCacheItem, DateTime.UtcNow.AddMinutes(5));
            _provider.Add(outputCacheKey3, outputCacheItem, DateTime.UtcNow.AddMinutes(5));
            object result = _provider.Get(outputCacheKey);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            result = _provider.Get(outputCacheKey2);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            result = _provider.Get(outputCacheKey3);
            Assert.IsNotNull(result);
            Assert.AreEqual(result, outputCacheItem);

            _manager.AddOutputCacheDependency(url);

            DependencyCache[OutputCache.CacheKey] = outputCacheKey;
            _manager.FinalizeDependencies();
            DependencyCache[OutputCache.CacheKey] = outputCacheKey2;
            _manager.FinalizeDependencies();
            DependencyCache[OutputCache.CacheKey] = outputCacheKey3;
            _manager.FinalizeDependencies();
            result = _provider.Get(url);
            Assert.IsInstanceOfType(result, typeof(CacheDependencyItem));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey2));
            Assert.IsTrue(((CacheDependencyItem)result).RelatedOutputCacheKeys.Contains(outputCacheKey3));

            // Remove it
            _provider.Remove(url);
            result = _provider.Get(outputCacheKey);
            Assert.IsNull(result);
            result = _provider.Get(outputCacheKey2);
            Assert.IsNull(result);
            result = _provider.Get(outputCacheKey3);
            Assert.IsNull(result);
        }
    }
}
